package stardust.core.support.db;


import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.*;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Logger;
import javax.sql.DataSource;
import stardust.core.log.Log;



/**
 * XiaoYuanDataSource 数据源，缓冲数据连接，并防止数据连接泄漏。
 * <p/>
 * @author 刘晓园
 * @version 1.0
 */
public class XiaoYuanDataSource implements DataSource
{

  private static final Log log = new Log(XiaoYuanDataSource.class);

  /**
   * 数据连接的 url、账户、密码、驱动程序。
   */
  private String url;

  private String username;

  private String password;

  private String driverClass;

  /**
   * 缓冲数据连接的数量。
   */
  private int cacheCount;

  /**
   * 最大并发数据连接的数量。
   */
  private int maxCount;

  /**
   * 数据连接状态更新间隔。
   */
  private int refreshTime;

  /**
   * 等待数据连接超时时间[单位:毫秒](为零时表示无限期等待；为负数表示立刻返回)
   */
  private int waitTime;

  /**
   * 已分配数据连接。
   */
  private List<Connection> connections;

  /**
   * 受缓冲的数据连接。
   */
  private List<Connection> cacheConnections;

  /**
   * 缓冲数据连接的线程，它通过轮询来维护缓冲。
   */
  private Thread dataSourceManagerThread;

  /**
   * 获取一个默认参数对数据源，它的特性和使用 DriverManager.getConnection 方法没有区别。
   */
  public XiaoYuanDataSource()
  {
    // 连接 URL
    this.setUrl("");

    // 帐户
    this.setUsername("");

    // 密码
    this.setPassword("");

    // 数据连接驱动程序
    this.setDriverClass("");

    // 不缓存数据连接
    this.setCacheCount(0);

    // 没有数据连接上限
    this.setMaxCount(0);

    // 间隔 500ms 对受管理的数据连接状态进行检测
    this.setRefreshTime(20);

    // 等待数据连接上限为 10 秒
    this.setWaitTime(10 * 1000);

    // 打开数据连接上限为 10 秒
    this.setLoginTimeout(10);

    // 已分配数据连接
    this.connections = Collections.synchronizedList(new LinkedList<Connection>());

    // 缓存数据连接
    this.cacheConnections = Collections.synchronizedList(new LinkedList<Connection>());

    // 注册虚拟机终止事件来关闭数据连接
    Runtime.getRuntime().addShutdownHook(new Thread(new DataSourceFinalizer(this)));
  }

  /**
   * 通过 Map 对数据源进行初始化
   * <p/>
   * @param parameters
* 包含初始化参数的 Map
   */
  private XiaoYuanDataSource(Properties parameters)
  {
    this();

    if (parameters.get("url") != null)
    {
      // 连接 URL
      this.setUrl((String) parameters.get("url"));
    }
    if (parameters.get("username") != null)
    {
      // 帐户
      this.setUsername((String) parameters.get("username"));
    }
    if (parameters.get("password") != null)
    {
      // 密码
      this.setPassword((String) parameters.get("password"));
    }
    if (parameters.get("driverClass") != null)
    {
      // 数据连接驱动程序
      this.setDriverClass((String) parameters.get("driverClass"));
    }
    if (parameters.get("cacheCount") != null)
    {
      // 缓存数据连接数量
      this.setCacheCount(Integer.parseInt((String) parameters.get("cacheCount")));
    }
    if (parameters.get("maxCount") != null)
    {
      // 活动的数据连接上限
      this.setMaxCount(Integer.parseInt((String) parameters.get("maxCount")));
    }
    if (parameters.get("refreshTime") != null)
    {
      // 管理的数据连接状态进行检测间隔
      this.setRefreshTime(Integer.parseInt((String) parameters.get("refreshTime")));
    }
    if (parameters.get("waitTime") != null)
    {
      // 等待数据连接超时设置
      this.setWaitTime(Integer.parseInt((String) parameters.get("waitTime")));
    }
    if (parameters.get("loginTimeout") != null)
    {
      // 打开数据连接超时设置
      this.setLoginTimeout(Integer.parseInt((String) parameters.get("loginTimeout")));
    }
  }

  /**
   * 通过 Properties 配置文件初始化数据源
   * <p/>
   * @param propertiesFile
   * 配置文件路径
   */
  public XiaoYuanDataSource(String propertiesFile)
  {
    this(new Object()
    {

      public Properties load(String propertiesFile)
      {
        Properties p = new Properties();

        try
        {
          p.load(XiaoYuanDataSource.class.getClassLoader().getResourceAsStream(propertiesFile));
        }
        catch(FileNotFoundException e)
        {
          throw new RuntimeException(e);
        }
        catch(IOException e)
        {
          throw new RuntimeException(e);
        }

        return p;
      }

    }.load(propertiesFile));
  }
  
  public void initial()
  {
    /*
     * 启动数据源管理线程
     */
    if (dataSourceManagerThread == null)
    {
      /*
       * 配置数据源管理线程，在第一次调用 getConnection 方法时启动
       */
      dataSourceManagerThread = new Thread(new DataSourceManagerThread(this));
      dataSourceManagerThread.setName("DataSourceManagerThread:" + this.toString());
      dataSourceManagerThread.setDaemon(true);
      dataSourceManagerThread.start();
    }
  }
  
  /**
   * 关闭连接（复用）。
   * <p/>
   * @param connection
   * @throws SQLException 
   */
  public void close(Connection connection) throws SQLException
  {
    if (!connection.isClosed())
    {
      connection.rollback();
      this.cacheConnections.add(connection);
    }
  }

  /**
   * 获取数据连接
   * <p/>
   * @return 数据连接
   * <p/>
   * @throws ClassNotFoundException
   */
  public Connection getConnection()
  {
    Connection conn;

    /*
     * 启动数据源管理线程
     */
    if (dataSourceManagerThread == null)
    {
      /*
       * 配置数据源管理线程，在第一次调用 getConnection 方法时启动
       */
      dataSourceManagerThread = new Thread(new DataSourceManagerThread(this));
      dataSourceManagerThread.setName("DataSourceManagerThread:" + this.toString());
      dataSourceManagerThread.setDaemon(true);
      dataSourceManagerThread.start();
    }

    /*
     * 立即返回或等待一段时间后返回数据连接 不可用并且等待超时返回 NULL
     */
    long startTime = System.currentTimeMillis();

    while ((conn = this.getCacheConnection()) == null
           && (this.waitTime == 0 || (this.waitTime > 0 && System.currentTimeMillis()
                                                           - startTime < this.waitTime)))
    {
      try
      {
        Thread.sleep(1);
      }
      catch(InterruptedException e)
      {
      }
    }

    if (conn == null)
    {
      return null;
    }
    else
    {
//       @todo trace
//       log.trace("获取连接：" + this.url);
    	
      return conn;
    }
  }

  /**
   * 获取数据连接
   * <p/>
   * @param username
* 帐户
   * @param password
* 密码
   * <p/>
   * @return 数据连接
   * <p/>
   * @throws ClassNotFoundException
   */
  public Connection getConnection(String username, String password)
  {
    Connection conn;

    /*
     * 立即返回或等待一段时间后返回数据连接 不可用并且等待超时返回 null
     */
    long startTime = System.currentTimeMillis();

    while (this.connections.size() >= this.maxCount
           && (this.waitTime == 0 || (this.waitTime > 0 && System.currentTimeMillis()
                                                           - startTime < this.waitTime)))
    {
      try
      {
        Thread.sleep(1);
      }
      catch(InterruptedException e)
      {
      }
    }

    if (this.connections.size() < this.maxCount)
    {
      conn = createConnection(Thread.currentThread().getContextClassLoader(), this.url, username, password,
                              this.driverClass);
      this.connections.add(conn);

      return conn;
    }
    else
    {
      return null;
    }
  }

  /**
   * 获取缓存数据连接
   * <p>
   * 当 cacheCount 小于等于 0 时直接返回数据连接。
   * <p/>
   * @return 数据连接
   * <p/>
   * @throws ClassNotFoundException
   */
  private synchronized Connection getCacheConnection()
  {
    Connection conn;

    if (this.cacheConnections.size() > 0)
    {
      conn = (Connection) this.cacheConnections.get(0);
      this.cacheConnections.remove(conn);
      this.connections.add(conn);
    }
    else if (this.cacheCount <= 0 || this.maxCount <= 0 || this.connections.size() < this.maxCount)
    {
      conn = createConnection(Thread.currentThread().getContextClassLoader(), this.url, username, password,
                              this.driverClass);
      this.connections.add(conn);
    }
    else
    {
      conn = null;
    }

    return conn;
  }

  /**
   * 重新计算分配缓存数据连接
   * <p/>
   * @throws ClassNotFoundException
   */
  void processCache() throws ClassNotFoundException
  {
    this.refreshConnection();
    this.refreshCacheConnection();

    while ((this.cacheConnections.size() + this.connections.size() < this.getMaxCount() || this.getMaxCount() == 0)
           && this.cacheConnections.size() < this.getCacheCount())
    {
      this.cacheConnections.add(XiaoYuanDataSource.createConnection(
      Thread.currentThread().getContextClassLoader(), this.getUrl(), this.getUsername(), this.getPassword(), this.getDriverClass()));
    }
  }

  /**
   * 检测缓冲数据连接的状态
   */
  void refreshCacheConnection()
  {
    Connection conn;

    for (int i = this.cacheConnections.size() - 1; i >= 0; i--)
    {
      conn = (Connection) this.cacheConnections.get(i);

      try
      {
        if (conn.isClosed())
        {
          this.cacheConnections.remove(i);
        }
      }
      catch(SQLException e)
      {
      }
    }
    for (int i = this.cacheConnections.size() - 1; i > this.cacheCount; i--)
    {
      conn = (Connection) this.cacheConnections.get(i);
      try
      {
        conn.close();
      }
      catch(SQLException e)
      {
      }
      this.cacheConnections.remove(conn);
    }
  }

  /**
   * 检测已分配数据连接的状态
   */
  void refreshConnection()
  {
    Connection conn;

    for (int i = this.connections.size() - 1; i >= 0; i--)
    {
      conn = (Connection) this.connections.get(i);

      try
      {
        if (conn.isClosed())
        {
          this.connections.remove(i);
          // @todo trace
          // log.trace("关闭连接：" + this.url);
        }
      }
      catch(SQLException e)
      {
      }
    }
  }

  /**
   * 清理数据连接
   */
  protected synchronized void finalize()
  {
    Connection conn;

    // 中断数据源管理线程
    if (this.dataSourceManagerThread != null)
    {
      this.dataSourceManagerThread.interrupt();
      // this.dataSourceManagerThread.stop();
    }

    /*
     * 清理已分配数据连接
     */
    for (int i = this.connections.size() - 1; i >= 0; i--)
    {
      conn = (Connection) this.connections.get(i);

      try
      {
        synchronized (conn)
        {
          conn.rollback();
          conn.close();
        }
        this.connections.remove(i);
      }
      catch(Exception e)
      {
      }
    }

    /*
     * 清理缓冲数据连接
     */
    for (int i = this.cacheConnections.size() - 1; i >= 0; i--)
    {
      conn = (Connection) this.cacheConnections.get(i);

      try
      {
        conn.close();
        this.cacheConnections.remove(i);
      }
      catch(SQLException e)
      {
      }
    }
  }

  /**
   * 创建一个新的数据连接
   * <p/>
   * @param url
     * 数据连接 URL
   * @param username
* 帐户
   * @param password
* 密码
   * <p/>
   * @return 数据连接
   * <p/>
   * @throws ClassNotFoundException
   */
  public static Connection createConnection(ClassLoader classLoader, String url, String username,
                                            String password, String driverClass)
  {
    Connection connection;
    Driver driver;
    Properties p = new Properties();
    p.put("user", username);
    p.put("password", password);

    try
    {
      driver = (Driver) (classLoader.loadClass(driverClass)).newInstance();
      connection = driver.connect(url, p);
      connection.setAutoCommit(false);

      // @todo trace
      // log.trace("缓存连接：" + url);

      return connection;
    }
    catch(Exception e)
    {
      throw new RuntimeException("无法创建数据连接。", e);
    }
  }

  public String getDriverClass()
  {
    return driverClass;
  }

  public void setDriverClass(String driverClass)
  {
    this.driverClass = driverClass;
  }

  public String getPassword()
  {
    return password;
  }

  public void setPassword(String password)
  {
    this.password = password;
  }

  public String getUrl()
  {
    return url;
  }

  public void setUrl(String url)
  {
    this.url = url;
  }

  public String getUsername()
  {
    return username;
  }

  public void setUsername(String username)
  {
    this.username = username;
  }

  public int getCacheCount()
  {
    return cacheCount;
  }

  public void setCacheCount(int cacheCount)
  {
    this.cacheCount = cacheCount;
  }

  public int getMaxCount()
  {
    return maxCount;
  }

  public void setMaxCount(int maxCount)
  {
    this.maxCount = maxCount;
  }

  public int getRefreshTime()
  {
    return refreshTime;
  }

  public void setRefreshTime(int refreshTime)
  {
    this.refreshTime = refreshTime;
  }

  public int getWaitTime()
  {
    return waitTime;
  }

  public void setWaitTime(int waitTime)
  {
    this.waitTime = waitTime;
  }

  public PrintWriter getLogWriter() throws SQLException
  {
    return DriverManager.getLogWriter();
  }

  public void setLogWriter(PrintWriter out) throws SQLException
  {
    DriverManager.setLogWriter(out);
  }

  public int getLoginTimeout()
  {
    return DriverManager.getLoginTimeout();
  }

  public void setLoginTimeout(int seconds)
  {
    DriverManager.setLoginTimeout(seconds);
  }

  public boolean isWrapperFor(Class<?> iface) throws SQLException
  {
    throw new UnsupportedOperationException("不支持 isWrapperFor 操作。");
  }

  public <T> T unwrap(Class<T> iface) throws SQLException
  {
    throw new UnsupportedOperationException("不支持 unwrap 操作。");
  }

  public Logger getParentLogger() throws SQLFeatureNotSupportedException
  {
    throw new UnsupportedOperationException("不支持 getParentLogger 操作。");
  }

}

/**
 * 数据源管理线程类
 */
class DataSourceManagerThread implements Runnable
{

  private XiaoYuanDataSource dataSource;

  public DataSourceManagerThread(XiaoYuanDataSource dataSource)
  {
    this.dataSource = dataSource;
  }

  public void run()
  {
    while (!Thread.currentThread().isInterrupted())
    {
      try
      {
        this.dataSource.processCache();
      }
      catch(ThreadDeath e)
      {
        this.dataSource.finalize();
        return;
      }
      catch(Throwable e)
      {
      }

      try
      {
        Thread.sleep(this.dataSource.getRefreshTime());
      }
      catch(InterruptedException e)
      {
        continue;
      }
    }
  }

}

/**
 * 数据源残留清理线程
 */
class DataSourceFinalizer implements Runnable
{

  private XiaoYuanDataSource dataSource;

  public DataSourceFinalizer(XiaoYuanDataSource dataSource)
  {
    this.dataSource = dataSource;
  }

  public void run()
  {
    this.dataSource.finalize();
  }

}
